Java JWT
Java版本的对于JSON Web Token(即 JWT )的实现
安装导入依赖
Maven
1 | <dependency> |
Gradle
1 | implementation 'com.auth0:java-jwt:3.13.0' |
支持的算法
JWS | Algorithm | Description |
---|---|---|
HS256 | HMAC256 | HMAC with SHA-256 |
HS384 | HMAC384 | HMAC with SHA-384 |
HS512 | HMAC512 | HMAC with SHA-512 |
RS256 | RAS256 | RSASSA-PKCS1-v1_5 with SHA-256 |
RS384 | RAS384 | RSASSA-PKCS1-v1_5 with SHA-384 |
RS512 | RAS512 | RSASSA-PKCS1-v1_5 with SHA-512 |
ES256 | ECDSA256 | ECDSA with curve P-256 and SHA-256 |
ES256K | ECDSA256 | ECDSA with curve secp256k1 and SHA-256 |
ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 |
ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 |
用法
挑选算法
算法决定了token怎么被签名和认证。
使用静态的 secrets 或 keys
1 | //HMAC |
使用一个 KeyProvider
通过使用 KeyProvider,你可以在运行时改变用RSA或者ECDSA算法验证或者签署新 token 时使用的 key ,通过使用 RSAKeyProvider 或者 ECDSKKeyProvider 等方法可以做到
- getPublicKeyById(String kid): 在 token 签名验证时调用,返回用于验证当前 token 的 key 。如果 key rotation 正在被使用(如: JWK),那么它可以通过 id 取回正确的 rotation key ,或者始终返回同一个 key
- getPrivateKey(): 在签署 token 时调用,会返回将要用于签署 JWT 的 key。
- getPrivateKeyId(): 在签署 token 时调用,会返回可以确认 getPrivateKey() 方法返回的 key 的 id 。这个值相较于 JWTCreator.Builder#withKeyId(String) 方法中的值优先级高。如果你不需要设置一个 kid 值,避免使用 KeyProvider 声明算法。
下面这个例子展示了 KeyProvider 如何在 JwkStore 一个梦幻的JWK SET 实现中工作。
例如简单的使用 JWKS 的 key rotation ,使用 jwks-rsa-java library。
1 | final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}"); |
创建并签署一个 Token
首先你必须调用 JWT.create() 方法创建一个 JWTCreator实例。利用这个构筑器确定你需要使用的 token 的样式。最后,通过 sign() 方法获得 String 类型的 token,把该 token 传递给算法的实例
- 例:使用 HS256 算法
1 | try { |
- 例:使用 RS256 算法
1 | RSAPublicKey publicKey = //Get the key instance |
如果一个样式无法被转化为 JSON 格式,或者用在签署过程中的 Key 是无效的,那么一个 JWTCreationException 异常将会产生
验证一个 Token
首先需要做的,是通过调用 JWT.require 方法创建一个 JWTVerifier的实例并把算法的实例传递给它。如果你需要 token 携带特殊的样式的值,使用构筑器去定义它们。通过 build() 方法返回的实例是可以被重复使用的,因此你可以每次定义实例去验证不同的 token,最后使用 verifier.verify() 方法传递 token。
- 例:使用 HS256 算法
1 | String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; |
- 例:使用 RS256 算法
1 | String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; |
如果 token 携带无效的签名,或者宣称的样式不存在,一个 JWTVerificationException 异常将会产生
有效时间(Time Validation)
JWK token 可以携带 日期数字字段(DateNumber),以用来证实如下信息
- token 在过去的某一刻被颁布
"iat" < TODAY
- token 仍未失效(未超过有效期时间)
"exp" > TODAY
- token 可能已经被使用过了
"nbf" < TODAY
当确认自动产生的的 token 有效期时,如果值是无效的,那么会导致抛出 JWTVerificationException 异常。如果任何之前的域丢失,它们不会在确认 token 时被考虑。
为了指定一个 leeway window ,确定在哪些地方 token 应该被确认是否有效,可以使用 在 JWTVerifier 构筑器中的 acceptLeeway() 方法然后传递一个确定的毫秒值(seconds value)。这对任何以上列出的都生效。
1 | JWTVerifier verifier = JWT.require(algorithm) |
也可以通过给定的日期样式(Date claim),和仅重写那个默认的描述来区分一个特殊的值
1 | JWTVerifier verifier = JWT.require(algorithm) |
如果需要测试你的 lib/abb 的运行状况,可以通过把 Verification 实例转型为 BaseVerificaion 来访问 可以接受自定义的 Clock 的 verification.build() 方法。
token的解码(Decode a Token)
1 | String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; |
如果 token 使用无效的语法或者它的头部(header)或者 携带部分(payload)不是 JSON 格式,一个 JWTDecodeException异常将会产生
token的头部定义(Header Claims)
算法部分 Algorithm(“alg”)
如果定义则返回算法类型,否则返回 null
1 | String algorithm = jwt.getAlgorithm(); |
类型部分 Type(“typ”)
如果未定义则返回 Type ,否则返回 null
1 | String type = jwt.getType(); |
内容类型 Content Type(“cty”)
如果未定义则返回 Content Type,否则返回 null
1 | String contentType = jwt.getContentType(); |
Key Id(“kid”)
如果未定义则返回 Key Id,否则返回 null
1 | String keyId = jwt.getKeyId(); |
私有自定义部分 Private Claims
可以通过 getHeaderClaim() 方法在 token 的头部添加额外的 Claim 和 Claim name。就算无法被找到,一个 Claim也会被返回。可以通过 claim.isNull() 方法检查 Claim 得值是否为空
1 | Claim claim = jwt.getHeaderClaim("owner"); |
当使用 JWT.create() 方法创建 Token时,你可以通过 withHeader() 以 map 集合方式传递一系列 claim 来指定 header claims
1 | Map<String, Object> headerClaims = new HashMap(); |
token的携带部分定义(Payload Claims)
发行人 Issuer(“iss”)
如果未定义则返回 Issuer ,否则返回 null
1 | String issuer = jwt.getIssuer(); |
主题 Subject(“sub”)
如果未定义则返回 Subject value ,否则返回 null
1 | String subject = jwt.getSubject(); |
Audience(“aud”)
如果未定义则返回 Audience value ,否则返回 null
1 | List<String> audience = jwt.getAudience(); |
过期时间 Expiration Time(“exp”)
如果未定义则返回 Expiration Time value ,否则返回 null
1 | Date expiresAt = jwt.getExpiresAt(); |
Not Before(“nbf”)
如果未定义则返回 Not Before value ,否则返回 null
1 | Date notBefore = jwt.getNotBefore(); |
Issued At(“iat”)
如果未定义则返回 Issued At value ,否则返回 null
1 | Date issuedAt = jwt.getIssuedAt(); |
JWT Id(“jti”)
如果未定义则返回 JWT ID value ,否则返回 null
1 | String id = jwt.getId(); |
Private Claims
通过 getClaims() 或着 getClaim() 方法并传递 Claim name 可以获得 token 的 Payload 中额外的 Claim。一个 Claim通常会被返回,除非它不存在。可以通过调用 claim.isNull() 来检查 Claim 的值是否为 null 。
1 | Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name |
或者
1 | Claim claim = jwt.getClaim("isAdmin"); |
当通过 JWT.create() 创建 token 时,也可以通过调用 withClaim() 方法并传递 Claim 的 name 与 value 来指定一个 Claim
1 | String token = JWT.create() |
Claim Class
Claim class 是 Claim values 的封装。它允许你以不同的类型(different class types)获取 claim 。
基本方法
- **asBoolean()**:返回Boolean类型的值,如不能转型则返回 null
- **asInt()**:返回 Integer 类型的值,如不能转型则返回 null
- **asDouble()**:返回 Double 类型的值,如不能转型则返回 null
- asLong:返回 Long 类型的值,如不能转型则返回 null
- asString:返回 String 类型的值,如不能转型则返回 null
- **asDate()**:返回 Date 类型的值,如不能转型则返回 null 。必须是一个 NumericDate(Unix Epoch/Timestamp)。注:JWT Standard 指定了所有的 NumericDate values 必须以秒(seconds)为单位